/*
 * @name: chacc
 * @description: Charging station smart contract for the research on blockchain-based data sharing scheme for Internet of Vehicles.
 * @author: Lulu Zhao
 */

package main

import (
	"encoding/json"
	"fmt"
	"github.com/hyperledger/fabric-chaincode-go/shim"
	"github.com/hyperledger/fabric-contract-api-go/contractapi"
)

// SmartContract provides functions for managing a charging station.
type SmartContract struct {
	contractapi.Contract
}

// ChargingStation describes basic details of what makes up a charging station.
type ChargingStation struct {
	ChargingStationID string `json:"ChargingStationID"`
	Location          string `json:"Location"`
	Brand             string `json:"Brand"`
	Provider          string `json:"Provider"`
	State             string `json:"State"`
}

// QueryResult structure used for handling result of query.
type QueryResult struct {
	Key    string `json:"Key"`
	Record *ChargingStation
}

// InitLedger adds a base set of charging stations to the ledger.
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
	chargingstations := []ChargingStation{
		ChargingStation{ChargingStationID: "0001", Location: "1,1", Brand: "Huawei", Provider: "Huawei(Beijing)", State: "free"},
		ChargingStation{ChargingStationID: "0002", Location: "1,1", Brand: "Huawei", Provider: "Huawei(Beijing)", State: "inuse"},
		ChargingStation{ChargingStationID: "0003", Location: "1,1", Brand: "Huawei", Provider: "Huawei(Beijing)", State: "booked"},
		ChargingStation{ChargingStationID: "0004", Location: "2,1", Brand: "Mi", Provider: "Mi(Beijing)", State: "free"},
		ChargingStation{ChargingStationID: "0005", Location: "2,1", Brand: "Mi", Provider: "Mi(Beijing)", State: "inuse"},
		ChargingStation{ChargingStationID: "0006", Location: "2,1", Brand: "Mi", Provider: "Mi(Beijing)", State: "booked"},
		ChargingStation{ChargingStationID: "0007", Location: "3,1", Brand: "Apple", Provider: "Apple(Beijing)", State: "free"},
		ChargingStation{ChargingStationID: "0008", Location: "3,1", Brand: "Apple", Provider: "Apple(Beijing)", State: "inuse"},
		ChargingStation{ChargingStationID: "0009", Location: "3,1", Brand: "Apple", Provider: "Apple(Beijing)", State: "booked"},
		ChargingStation{ChargingStationID: "0010", Location: "4,1", Brand: "OPPO", Provider: "OPPO(Beijing)", State: "free"},
		ChargingStation{ChargingStationID: "0011", Location: "4,1", Brand: "OPPO", Provider: "OPPO(Beijing)", State: "inuse"},
		ChargingStation{ChargingStationID: "0012", Location: "4,1", Brand: "OPPO", Provider: "OPPO(Beijing)", State: "booked"},
	}
	for i, chargingstation := range chargingstations {
		chargingstationAsBytes, _ := json.Marshal(chargingstation)
		err := ctx.GetStub().PutState(chargingstations[i].ChargingStationID, chargingstationAsBytes)
		if err != nil {
			return fmt.Errorf("failed to put to world state %s", err.Error())
		}
	}
	return nil
}

// RegisterChargingStation adds a new charging station to the world state with given details.
func (s *SmartContract) RegisterChargingStation(ctx contractapi.TransactionContextInterface, chargingstationid string, location string, brand string, provider string, state string) error {
	chargingstation := ChargingStation{
		ChargingStationID: chargingstationid,
		Location:          location,
		Brand:             brand,
		Provider:          provider,
		State:             state,
	}
	chargingstationAsBytes, _ := json.Marshal(chargingstation)
	return ctx.GetStub().PutState(chargingstationid, chargingstationAsBytes)
}

// QueryChargingStation returns the charging station stored in the world state with given id
func (s *SmartContract) QueryChargingStation(ctx contractapi.TransactionContextInterface, chargingstationid string) (*ChargingStation, error) {
	chargingstationAsBytes, err := ctx.GetStub().GetState(chargingstationid)
	if err != nil {
		return nil, fmt.Errorf("failed to read from world state %s", err.Error())
	}
	if chargingstationAsBytes == nil {
		return nil, fmt.Errorf("%s does not exist", chargingstationid)
	}
	chargingstation := new(ChargingStation)
	_ = json.Unmarshal(chargingstationAsBytes, chargingstation)
	return chargingstation, nil
}

// QueryAllChargingStations returns all charging stations found in world state.
func (s *SmartContract) QueryAllChargingStations(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) {
	startKey := ""
	endKey := ""
	resultIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)
	if err != nil {
		return nil, fmt.Errorf("failed to read from world state %s", err.Error())
	}
	defer func(resultIterator shim.StateQueryIteratorInterface) {
		err := resultIterator.Close()
		if err != nil {
			_ = fmt.Errorf("failed to close result iterator")
		}
	}(resultIterator)
	var results []QueryResult
	for resultIterator.HasNext() {
		queryResponse, err := resultIterator.Next()
		if err != nil {
			return nil, err
		}
		chargingstation := new(ChargingStation)
		_ = json.Unmarshal(queryResponse.Value, chargingstation)
		queryResult := QueryResult{Key: queryResponse.Key, Record: chargingstation}
		results = append(results, queryResult)
	}
	return results, nil
}

// UpdateVehicleState updates the state field of the charging station with given id.
func (s *SmartContract) UpdateVehicleState(ctx contractapi.TransactionContextInterface, chargingstationid string, location string, brand string, provider string, state string) error {
	chargingstation, err := s.QueryChargingStation(ctx, chargingstationid)
	if err != nil {
		return err
	}
	chargingstation.ChargingStationID = chargingstationid
	chargingstation.Location = location
	chargingstation.Brand = brand
	chargingstation.Provider = provider
	chargingstation.State = state
	chargingstationAsBytes, _ := json.Marshal(chargingstation)
	return ctx.GetStub().PutState(chargingstationid, chargingstationAsBytes)
}

func main() {
	chaincode, err := contractapi.NewChaincode(new(SmartContract))
	if err != nil {
		fmt.Printf("error create vehcc chaincode: %s", err.Error())
		return
	}
	if err := chaincode.Start(); err != nil {
		fmt.Printf("error starting vehcc chaincode: %s", err.Error())
	}
}
